// =====================================================================================
//
//       Filename:  geometrie.cpp
//
//    Description:  Implémentation des structures géométriques utilisées pour la
//					géolocalisation des objets de la carte
//
//        Version:  1.0
//        Created:  11/08/2009 18:03:00
//       Revision:  none
//       Compiler:  g++
//
//         Author:  François Hissel (fh), francois.hissel@m4x.org
//        Company:
//
// =====================================================================================

#include	<iostream>
#include	<deque>
#include	<cmath>
#include	<cfloat>
#include	<exception>
#include	<string>
#include	"geometrie.h"

using namespace std;

/*************************************************/
/* IMPLEMENTATION DE LA CLASSE GEOMETRYEXCEPTION */
/*************************************************/
const char* GeometryException::what() const throw() {
	switch (code) {
		case BAD_OPERATION:return ("Opération incorrecte : "+message).c_str();
		default:return message.c_str();
	}
}

/*************************************/
/* IMPLEMENTATION DE LA CLASSE POINT */
/*************************************/
void Point::affiche(ostream &out) const {
	out << "(" << x << "," << y << ")";
}

void Point::coordonnees(double res[]) const {
	res[0]=x;res[1]=y;
}

double Point::carre_distance(const Point &p) const {
	double dx=p.x-x;
	double dy=p.y-y;
	return dx*dx+dy*dy;
}

double Point::carre_distance(const Point &a,const Point &b) const {
	double nx=a.y-b.y;	//On calcule les coordonnées de n, vecteur normal à la droite (ab)
	double ny=b.x-a.x;
	double n=nx*nx+ny*ny;
	double d=(b.x-x)*nx+(b.y-y)*ny;	//Alors (bm,n)=(hm,n)
	return d*d/n;
}

double Point::carre_distance_segment(const Point &a,const Point &b) const {
	double vx=b.x-a.x;
	double vy=b.y-a.y;
	double scalar=(x-a.x)*vx+(y-a.y)*vy;	//On calcule le produit scalaire (am,ab)
	if (scalar<0) return carre_distance(a);	//Si le produit scalaire est négatif, le point m est plus proche du point a que du reste du segment
	if (scalar>vx*vx+vy*vy) return carre_distance(b);	//Si le produit scalaire est supérieur à ab^2, le point m est plus proche du point b que du reste du segment
	return carre_distance(a,b);	//Sinon le projeté de m sur la droite (ab) est bien sur le segment [ab] et la distance est donc égale à la distance à la droite (ab)
}

bool operator< (const Point &p1,const Point &p2) {
	return (p1.x<p2.x || (p1.x==p2.x && p1.y<p2.y));
}

bool operator== (const Point &p1,const Point &p2) {
	return (p1.x==p2.x && p1.y==p2.y);
}
/*****************************************/
/* IMPLEMENTATION DE LA CLASSE POLYLIGNE */
/*****************************************/
Polyligne::Polyligne(const Polyligne &p) {
	for (int i=0;i<3;++i) attributs[i]=p.attributs[i];
	for (deque<Point*>::const_iterator it=p._points.begin();it!=p._points.end();it++) {
		_points.push_back(new Point(**it));
	}
}

Polyligne::~Polyligne() {
	vide();
}

void Polyligne::affiche(ostream &out) const {
	out << "[";
	for (int i=0;i<2;++i) out << attributs[i] << ",";
	out << attributs[2] << "] - ";
	for (deque<Point*>::const_iterator it=_points.begin();it!=_points.end();it++) {
		out << (**it) << ",";
	}
}

void Polyligne::vide() {
	for (deque<Point*>::iterator it=_points.begin();it!=_points.end();it++) delete (*it);
	_points.clear();
	_longueur=0;
}

void Polyligne::ajoute_point(Point *p) {
	if (_points.size()>=1) _longueur+=sqrt(p->carre_distance(*(_points.back())));
	_points.push_back(p);
}

/*double Polyligne::longueur() const {
	if (_points.size()<2) return 0;
	deque<Point*>::const_iterator it=_points.begin();
	Point *p=*it;
	it++;
	double lon=0.0;
	while (it!=points.end()) {
		lon+=sqrt(p->carre_distance(**it));
		p=*it;
		it++;
	}
	return lon;
}*/

double Polyligne::distance(const Point &p) const {
	if (_points.size()==0) throw GeometryException(GeometryException::BAD_OPERATION,"Ligne vide");
	if (_points.size()==1) return p.distance(*(_points.front()));
	deque<Point*>::const_iterator it=_points.begin();
	Point *q=*it;
	double d2=p.carre_distance(*q);	// d2 contient la distance du point courant p au premier point q
	it++;
	double min=DBL_MAX;	//Contient la distance minimale aux segments de la polyligne déjà parcourus
	while (it!=_points.end()) {
		Point *r=*it;
		double vx=r->x-q->x;	//A chaque itération, vx et vy contiennent les coordonnées du vecteur qr
		double vy=r->y-q->y;
		double scal=(p.x-q->x)*vx+(p.y-q->y)*vy;	//Produit scalaire (qp,qr), utilisé pour tester l'appartenance au segment [qr] du projeté de p sur la droite (qr)
		double d;
		double d3=p.carre_distance(*r);
		if (scal<=0) d=d2;
		else if (scal>vx*vx+vy*vy) d=d3;
		else d=p.carre_distance(*q,*r);
		if (min<d) min=d;
		d2=d3;
		q=*it;
		it++;
	}
	return sqrt(min);
}
